package com.information.manage.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.information.manage.basic.common.result.Result;
import com.information.manage.basic.common.token.TokenUtils;
import com.information.manage.basic.common.utils.PathUtils;
import com.information.manage.basic.common.utils.UUIDUtils;
import com.information.manage.basic.model.vo.DepartmentTreeVO;
import com.information.manage.common.constants.AddFileStatusEnum;
import com.information.manage.common.constants.HandleFileTypeEnum;
import com.information.manage.common.constants.ListenTypeEnum;
import com.information.manage.dao.AddFileMapper;
import com.information.manage.model.dto.ClassifyStatisticDTO;
import com.information.manage.model.entity.AddFileDO;
import com.information.manage.model.query.AddFileQuery;
import com.information.manage.model.vo.AllStatisticVO;
import com.information.manage.service.AddFileService;
import com.information.manage.service.CommonService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

@Slf4j
@Service
public class AddFileServiceImpl extends ServiceImpl<AddFileMapper, AddFileDO> implements AddFileService {

    @Value("${base.watchDirPath:/usr/local/tmp}")
    private String watchDirPath;

    @Value("${base.generate.upload.suffix:_upload}")
    private String generateUploadSuffix;

    @Value("${base.generate.upload.maxCount:10}")
    private Integer uploadMaxCount;

    @Resource
    private AddFileMapper addFileMapper;

    @Autowired
    private CommonService commonService;

    @Override
    public Result<Page<AddFileDO>> queryPage(AddFileQuery addFileQuery) {
        Page<AddFileDO> addFileDOPage = addFileMapper.queryPage(new Page<>(addFileQuery.getCurPage(), addFileQuery.getPageSize())
                , addFileQuery.getListenFileTypeCode(), addFileQuery.getType(), addFileQuery.getStatus(), addFileQuery.getCreateStartTime()
                , addFileQuery.getCreateEndTime());
        List<AddFileDO> records = addFileDOPage.getRecords();
        if (CollectionUtils.isNotEmpty(records)){
            for (AddFileDO record : records) {
                // 处理上传的文件名称
                record.setFileName(record.getRealFileName());
            }
        }
        return Result.ok(addFileDOPage);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<List<AddFileDO>> handleFileUpload(List<MultipartFile> archives, Integer listenFileTypeCode, HttpServletRequest request) {
        // 限制文件上传最大数量
        Assert.isTrue(archives.size() <= uploadMaxCount, "超出最大上传量：" + uploadMaxCount);
        Integer userId = TokenUtils.getUserId(StringUtils.isEmpty(request.getHeader("token")) ? request.getAttribute("token").toString() : request.getHeader("token"));
        // 获取到监听文件的类型
        String fileDir = ListenTypeEnum.getFileDir(listenFileTypeCode);
        Path baseDirPath = Paths.get(PathUtils.builderPath(watchDirPath, fileDir));
        if (!Files.exists(baseDirPath)) {
            try {
                Files.createDirectories(baseDirPath);
            } catch (IOException e) {
                e.printStackTrace();
                return Result.fail("目录：【"+ watchDirPath + "】创建失败");
            }
        }

        List<AddFileDO> resultFailList = new ArrayList<>();
        List<AddFileDO> resultSuccessList = new ArrayList<>();
        for (MultipartFile archive : archives) {
            String originalFilename = archive.getOriginalFilename();
            // 去除后缀.tar或者.zip的文件名称
            String substring = originalFilename.substring(0, originalFilename.lastIndexOf("."));
            // 修改文件名称，防止出现相同的文件名，故使用fileName_uuid, 再区分上传人id, 故使用fileName_uuid_userId , 为了区分是上传的文件，故用fileName_uuid_userId加上generateUploadSuffix
            Assert.isTrue(!substring.endsWith(generateUploadSuffix), "文件名不能以[" + generateUploadSuffix + "]结尾");
            StringBuilder sb = new StringBuilder().append(substring).append("_").append(UUIDUtils.getRandomUUID()).append("_").append(userId).append(generateUploadSuffix);
            String builderPath = PathUtils.builderPath(watchDirPath, fileDir, sb.toString());

            try (InputStream is = archive.getInputStream()) {
                if (archive.getContentType().startsWith("application/x-tar")) {
                    // 先不处理tar格式的，因为目前上传走windows，基本都是zip格式
                    resultFailList.add(addUploadFail(originalFilename, Long.valueOf(userId), AddFileStatusEnum.UPLOAD_FAIL.getCode(), "不处理tar格式"));
                    continue;
                } else if (archive.getContentType().equals("application/x-zip-compressed")) {
                    // 如果是ZIP格式，可以使用类似的逻辑
                    unzipFile(is, builderPath);
                    resultSuccessList.add(addUploadFail(originalFilename, Long.valueOf(userId), AddFileStatusEnum.NOT_START.getCode(), org.apache.commons.lang3.StringUtils.EMPTY));
                }
            } catch (IOException e) {
                e.printStackTrace();
                resultFailList.add(addUploadFail(originalFilename, Long.valueOf(userId), AddFileStatusEnum.UPLOAD_FAIL.getCode(), e.getMessage()));
                continue;
            }

//            List<String> fileNames = null;
//            try {
//                fileNames = Files.walk(Paths.get(builderPath))
//                        .filter(path -> !Files.isDirectory(path))
//                        .map(path -> path.getFileName().toString())
//                        .collect(Collectors.toList());
//            } catch (IOException e) {
//                resultList.add(addUploadFail(originalFilename, Long.valueOf(userId), e.getMessage()));
//                continue;
//            }
            log.info("解析文件{}完成， 文件夹名称：{}", originalFilename, sb.toString());
        }

        if (CollectionUtils.isNotEmpty(resultFailList)){  // 存在失败的 则将失败的保存到数据库中   上传成功的不进行保存，会有后面检测的地方统一存储
            addFileMapper.insertList(resultFailList);
        }

        // 将成功与失败的结果返回
        resultSuccessList.addAll(resultFailList);
        return Result.ok(resultSuccessList);
    }

    /**
     * 添加上传失败
     */
    private AddFileDO addUploadFail(String originalFilename, Long userId, Integer status, String result){
        AddFileDO addFileDO = new AddFileDO();
        addFileDO.setFileName(originalFilename);
        addFileDO.setType(HandleFileTypeEnum.UPLOAD.getCode());
        addFileDO.setOperator(userId);
        addFileDO.setCreateTime(new Date());
        addFileDO.setStatus(status);
        addFileDO.setResult(result);
        return addFileDO;
    }

    /**
     * 从给定的InputStream解压ZIP文件到指定的目标目录。
     *
     * @param zipInputStream 输入流，指向ZIP文件。
     * @param targetDirectory 目标目录，ZIP文件将被解压到这里。
     * @throws IOException 如果处理文件或目录时出现错误。
     */
    public static void unzipFile(InputStream zipInputStream, String targetDirectory) throws IOException {
        // 创建目标目录如果不存在
        File dir = new File(targetDirectory);
        if (!dir.exists()) {
            dir.mkdirs();
        }

        // 解压错误时，可能时编码格式不对，需要替换
        // StandardCharsets.UTF_8, Charset.forName("GBK"), Charset.forName("GB2312"), Charset.forName("Big5"), Charset.forName("EUC-JP"), Charset.forName("Shift_JIS")
        try (ZipInputStream zis = new ZipInputStream(zipInputStream, Charset.forName("GB2312"))) {
            ZipEntry entry;
            while ((entry = zis.getNextEntry()) != null) {
                String filePath = targetDirectory + File.separator + entry.getName();
                if (!entry.isDirectory()) {
                    // 如果是文件，则创建并写入数据
                    extractFile(zis, filePath);
                } else {
                    // 如果是目录，则创建目录
                    File directory = new File(filePath);
                    directory.mkdirs();
                }
                zis.closeEntry();
            }
        } catch (IOException e){
            throw e;
        }
    }

    private static void extractFile(ZipInputStream zis, String filePath) throws IOException {
        try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath))) {
            byte[] bytesIn = new byte[4096];
            int read = 0;
            while ((read = zis.read(bytesIn)) != -1) {
                bos.write(bytesIn, 0, read);
            }
        }
    }

    @Override
    public Result<List<AllStatisticVO>> uploadStatistic(Integer days, Integer type) {
        List<DepartmentTreeVO> departmentTreeVOS = commonService.getDepartmentTreeVOList();
        if (CollectionUtils.isEmpty(departmentTreeVOS)) return Result.fail("暂不存在任何统计信息");

        Map<Long, ClassifyStatisticDTO> collect = addFileMapper.uploadStatistic(type, days).stream().collect(Collectors.toMap(ClassifyStatisticDTO::getClassifyId, v -> v));
        List<AllStatisticVO> list = commonService.recursionStatistic(departmentTreeVOS, collect);

        // 可能存在某些分类id在departmentTreeVOS中不存在，即可能某些分类id已经被删除，此时全部当作其他
        Set<Long> allIds = commonService.getAllIds(departmentTreeVOS);
        Set<Long> keySet = collect.keySet();
        keySet.removeAll(allIds);
        if (CollectionUtils.isNotEmpty(keySet)) {
            AllStatisticVO allStatisticVO = new AllStatisticVO();
            allStatisticVO.setName("其它");
            Long otherCount = 0L;
            for (Long aLong : keySet) {
                ClassifyStatisticDTO classifyStatisticDTO = collect.get(aLong);
                Long totalCount = classifyStatisticDTO.getTotalCount();
                otherCount += totalCount;
            }
            allStatisticVO.setAll(otherCount);
            list.add(allStatisticVO);
        }

        return Result.ok(list);
    }

}
